﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using ERF.Physics;

namespace ERF
{
    /// <summary>
    /// Represents a GameObject visualized by a Model.
    /// </summary>
    public abstract class ModelGameObject : GameObject {
        private Model model;
        protected bool useBasicEffect = true;
        private Vector3 direction;
        protected List<string> TranslucentSurfaces;
        protected EffectBase[][] meshEffects;

        /// <summary>
        /// Gets or sets the model used.
        /// </summary>
        public Model ObjectModel {
            get { return this.model; }
            set 
            {
                this.model = value;
                this.meshEffects = new EffectBase[this.model.Meshes.Count][];
                for (int i = 0; i < this.model.Meshes.Count; i++)
                    this.meshEffects[i] = new EffectBase[this.model.Meshes[i].MeshParts.Count];
            }
        }

        /// <summary>
        /// Creates a new instance of ModelGameObject.
        /// </summary>
        /// <param name="game">The game that this instance belongs to.</param>
        public ModelGameObject(ERF.ERFGame game)
            : base(game) {
            this.direction = Vector3.Forward;
            TranslucentSurfaces = new List<string>();
        }

        public override void Update(GameTime gameTime, ref Matrix world) {
            base.UpdateWorld(ref world);

            base.Update(gameTime, ref base.world);
        }

        /// <summary>
        /// Draws the model transparently.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void DrawTransparent(GameTime gameTime) {
            this.SetEffects();
            Camera camera = base.CurrentGame.Camera;
            this.model.Root.Transform = Matrix.Identity;
            Matrix[] boneTransforms = new Matrix[this.model.Bones.Count];
            this.model.CopyAbsoluteBoneTransformsTo(boneTransforms);

            for (int i = 0; i < this.model.Meshes.Count; i++) {
                if (TranslucentSurfaces.Contains(this.model.Meshes[i].Name)) {
                    if (this.useBasicEffect) {

                        foreach (BasicEffect effect in this.model.Meshes[i].Effects) {

                            effect.GraphicsDevice.RenderState.AlphaBlendEnable = true;
                            effect.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
                            effect.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
                            effect.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;

                            effect.EnableDefaultLighting();
                            effect.PreferPerPixelLighting = true;
                            effect.World =
                                boneTransforms[this.model.Meshes[i].ParentBone.Index] *
                                base.World;

                            effect.Projection = camera.Projection;
                            effect.View = camera.View;


                        }

                    }
                    this.model.Meshes[i].Draw();
                }
            }
            CurrentGame.GraphicsDevice.RenderState.AlphaBlendEnable = false;
            CurrentGame.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
        }

        /// <summary>
        /// Draws the model opaque.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void DrawSolid(GameTime gameTime) {
            this.SetEffects();
            Camera camera = base.CurrentGame.Camera;
            this.model.Root.Transform = Matrix.Identity;
            Matrix[] boneTransforms = new Matrix[this.model.Bones.Count];
            this.model.CopyAbsoluteBoneTransformsTo(boneTransforms);

            if (base.renderWithoutDepthBuffer)
                base.CurrentGame.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;

            // If model contains translucent meshes, we need to turn culling off.
            bool containsTranslucentMeshes = (this.TranslucentSurfaces.Count > 0);
            if (containsTranslucentMeshes)
                base.CurrentGame.GraphicsDevice.RenderState.CullMode = CullMode.None;


            for (int i = 0; i < this.model.Meshes.Count; i++) {
                if (!TranslucentSurfaces.Contains(this.model.Meshes[i].Name)) {
                    if (this.useBasicEffect) {
                        foreach (EffectBase effect in this.model.Meshes[i].Effects) {
                            effect.World =
                                boneTransforms[this.model.Meshes[i].ParentBone.Index] *
                                base.World;
                            effect.Projection = camera.Projection;
                            effect.View = camera.View;
                            effect.ViewDirection = camera.Position - camera.Target;
                            effect.EyePosition = camera.Position;
                            base.OnSetEffectParameters(effect);
                        }

                    }
                    this.model.Meshes[i].Draw();
                }
            }

            if (base.renderWithoutDepthBuffer)
                base.CurrentGame.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;

            // If culling was turned off earlier, we need to turn it back on.
            if (containsTranslucentMeshes)
                base.CurrentGame.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
        }

        private void SetEffects()
        {
            for (int i = 0; i < this.model.Meshes.Count; i++)
            {
                for (int j = 0; j < this.model.Meshes[i].MeshParts.Count; j++)
                {
                    this.model.Meshes[i].MeshParts[j].Effect = this.meshEffects[i][j];
                }
            }
        }

        public override void Dispose()
        {
            
        }
    }
}
